package service

import (
	"advertising/config/mysql"
	"advertising/internal/app/grpc/client"
	"advertising/internal/app/grpc/protoc/devicec"
	"advertising/internal/app/grpc/protoc/mini_app"
	"advertising/internal/dao"
	"advertising/internal/define"
	"advertising/internal/models"
	"advertising/internal/pkg"
	"context"
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/logx"
	"github.com/golang-module/carbon"
	"github.com/samber/lo"
	"gorm.io/gorm"
)

type PublishSchedule struct{}

// AuditingPublishScheduleCount 审核中计划统计
func (PublishSchedule) AuditingPublishScheduleCount(ctx context.Context, db *gorm.DB, merchantID string) (count int64, err error) {
	count, _ = dao.PublishSchedule{}.StatusCount(ctx, db, merchantID, AUDITING)
	return
}

// PublishingPublishScheduleCount 投放中计划统计
func (PublishSchedule) PublishingPublishScheduleCount(ctx context.Context, db *gorm.DB, merchantID string) (count int64, err error) {
	count, _ = dao.PublishSchedule{}.StatusCount(ctx, db, merchantID, PUBLISHING)
	return
}

type Materials []struct {
	MaterialID      string `json:"material_id"`
	Duration        int64  `json:"duration"`
	PlayType        int8   `json:"play_type"`
	RegularInterval int64  `json:"regular_interval"`
}

const (
	TOAUDIT      = iota + 1 // 未提审
	AUDITING                // 审核中
	PUBLISHING              // 投放中
	TOSTART                 // 待开始
	AUDIT_FAIL              // 审核失败
	FINISHED                // 已结束
	ORDER_FAILED            // 订单失败未启用
)

func CheckGender(gender string) (newGender string, err error) {
	if gender == "" {
		return gender, nil
	}
	regRes, _ := regexp.MatchString(`^\d+(,\d+)*$`, gender)
	if !regRes {
		return "", errorx.New("投放性别错误", -1)
	}
	genders := []string{}
	for _, v := range strings.Split(gender, ",") {
		if _, ok := define.GenderMap[v]; !ok {
			return "", errorx.New("投放性别错误", -1)
		}
		if !lo.Contains(genders, v) {
			genders = append(genders, v)
		}
	}
	sort.Slice(genders, func(i, j int) bool {
		return genders[i] < genders[j]
	})
	gender = strings.Join(genders, ",")
	return gender, nil
}

func CheckAge(age string) (newAge string, err error) {
	if age == "" {
		return age, nil
	}
	regRes, _ := regexp.MatchString(`^\d+(,\d+)*$`, age)
	if !regRes {
		return "", errorx.New("投放年龄错误", -1)
	}
	ages := []string{}
	for _, v := range strings.Split(age, ",") {
		if _, ok := define.AgeMap[v]; !ok {
			return "", errorx.New("投放年龄错误", -1)
		}
		if !lo.Contains(ages, v) {
			ages = append(ages, v)
		}
	}
	sort.Slice(ages, func(i, j int) bool {
		return ages[i] < ages[j]
	})
	age = strings.Join(ages, ",")
	return age, nil
}

func CheckTag(tag string) (newTag string, err error) {
	if tag == "" {
		return tag, nil
	}
	regRes, _ := regexp.MatchString(`^\d+(,\d+)*$`, tag)
	if !regRes {
		return "", errorx.New("投放标签错误", -1)
	}
	tags := []string{}
	for _, v := range strings.Split(tag, ",") {
		if _, ok := define.TagMap[v]; !ok {
			return "", errorx.New("投放标签错误", -1)
		}
		if !lo.Contains(tags, v) {
			tags = append(tags, v)
		}
	}
	sort.Slice(tags, func(i, j int) bool {
		return tags[i] < tags[j]
	})
	tag = strings.Join(tags, ",")
	return tag, nil
}

// 投放计划新增编辑
func (PublishSchedule) Save(ctx context.Context, db *gorm.DB, merchantID, merchantName, userID, userName string,
	ID string, position int8, name string, startTime, endTime int64, details string, materials Materials,
	gender, age, tag, devices string, publishType, detailsShow int8, publishCount int64, supportActivityID string) (id string, timeountRemind int, err error) {
	var totalDuration int64
	if position == 0 {
		return "", timeountRemind, errorx.New("投放位置不能为空", -1)
	}
	if position != 1 && position != 2 && position != 3 {
		return "", timeountRemind, errorx.New("投放位置错误", -1)
	}
	if strings.TrimSpace(name) == "" {
		return "", timeountRemind, errorx.New("计划名称不能为空", -1)
	}
	regRes, _ := regexp.MatchString("^[0-9a-zA-Z\u4e00-\u9fa5]{1,12}$", strings.TrimSpace(name))
	if !regRes {
		return "", timeountRemind, errorx.New("计划名称仅限中文英文及数字且最长12个字符", -1)
	}

	// 时间判断
	if startTime == 0 {
		return "", timeountRemind, errorx.New("投放周期开始时间不能为空", -1)
	}
	if endTime == 0 {
		return "", timeountRemind, errorx.New("投放周期结束时间不能为空", -1)
	}
	if endTime <= startTime {
		return "", timeountRemind, errorx.New("投放周期结束时间不能小于开始时间", -1)
	}
	if endTime-startTime > 366*86400 {
		return "", timeountRemind, errorx.New("投放周期最大选择366天", -1)
	}
	if ID == "" { // 新增
		// 1 刊例单素材 2精准 3刊例多素材
		if publishType != 1 && publishType != 2 && publishType != 3 { // 仅新增不允许编辑
			return "", timeountRemind, errorx.New("投放类型错误", -1)
		}
	} else { // 编辑
		scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, merchantID, ID)
		if scheduleInfo == nil || scheduleCount == 0 {
			return "", timeountRemind, errorx.New("投放计划不存在", -1)
		}
		if scheduleInfo.Status != TOAUDIT {
			return "", timeountRemind, errorx.New("投放计划的当前状态不允许编辑", -1)
		}
		publishType = scheduleInfo.PublishType
	}
	if detailsShow != 0 && detailsShow != 1 {
		return "", timeountRemind, errorx.New("素材详情页开关异常", -1)
	}
	// 素材判断
	if len(materials) == 0 {
		return "", timeountRemind, errorx.New("投放素材不能为空", -1)
	}
	if position == 1 { // 前屏售卖屏
		if publishType == 2 { // 精准
			gender, err = CheckGender(gender)
			if err != nil {
				return
			}
			age, err = CheckAge(age)
			if err != nil {
				return
			}
			tag, err = CheckTag(tag)
			if err != nil {
				return
			}
			if publishCount <= 0 || publishCount%100 != 0 {
				return "", timeountRemind, errorx.New("投放次数必须是100的倍数", -1)
			}
			if len(materials) > 1 {
				return "", timeountRemind, errorx.New("投放计划为售卖屏精准时,最多1张素材", -1)
			}
		} else if publishType == 1 { // 刊例单素材
			if len(materials) > 1 {
				return "", timeountRemind, errorx.New("投放计划为售卖屏单素材刊例时,最多1张素材", -1)
			}
		} else if publishType == 3 { // 刊例多素材
			if len(materials) > 5 {
				return "", timeountRemind, errorx.New("投放计划为售卖屏多素材刊例时,最多5张素材", -1)
			}
			detailsShow = 0
			details = ""
		}
		var materialIDList = []string{}
		for k, v := range materials {
			if v.MaterialID == "" {
				return "", timeountRemind, errorx.New("素材id不能为空", -1)
			}
			if v.Duration == 0 {
				return "", timeountRemind, errorx.New("素材投放时长必填", -1)
			}
			if lo.Contains(materialIDList, v.MaterialID) {
				return "", timeountRemind, errorx.New("素材id不能重复", -1)
			}
			if v.PlayType == 0 {
				v.PlayType = 1
			}
			if v.PlayType != 1 && v.PlayType != 2 && v.PlayType != 3 {
				return "", timeountRemind, errorx.New("播放模式不正确", -1)
			}
			if v.PlayType == 2 {
				if v.RegularInterval < 1 || v.RegularInterval > 5 {
					return "", timeountRemind, errorx.New("定时播放模式的定时间隔错误", -1)
				}
			} else {
				materials[k].RegularInterval = 0
			}
			materialIDList = append(materialIDList, v.MaterialID)
		}
		materialList, materialCount, _ := dao.Material{}.List(ctx, db, merchantID, strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		if materialCount > 0 {
			for _, v := range materialList {
				MaterialMap[v.ID] = *v
			}
		}
		for k, v := range materials {
			if _, ok := MaterialMap[v.MaterialID]; !ok {
				return "", timeountRemind, errorx.New(fmt.Sprintf("第%d个素材文件不存在", k+1), -1)
			}
			if MaterialMap[v.MaterialID].Type == 1 { // 图片
				if MaterialMap[v.MaterialID].Position != position {
					return "", timeountRemind, errorx.New("当前素材图片不是售卖屏素材图片", -1)
				}
				totalDuration += v.Duration
			} else {
				return "", timeountRemind, errorx.New("售卖屏投放计划仅限图片素材", -1)
			}
		}
	} else if position == 2 { // 后屏媒体屏
		if publishType == 2 { // 精准
			gender, err = CheckGender(gender)
			if err != nil {
				return
			}
			age, err = CheckAge(age)
			if err != nil {
				return
			}
			tag, err = CheckTag(tag)
			if err != nil {
				return
			}
			if publishCount <= 0 || publishCount%100 != 0 {
				return "", timeountRemind, errorx.New("投放次数必须是100的倍数", -1)
			}
			if len(materials) > 1 {
				return "", timeountRemind, errorx.New("投放计划为媒体屏精准时,最多1张素材", -1)
			}
		} else { // 刊例
			if len(materials) > 5 {
				return "", timeountRemind, errorx.New("投放计划为媒体屏刊例时,最多5张素材", -1)
			}
		}
		var materialIDList = []string{}
		for k, v := range materials {
			if v.MaterialID == "" {
				return "", timeountRemind, errorx.New("素材id不能为空", -1)
			}
			if v.Duration == 0 {
				return "", timeountRemind, errorx.New("素材投放时长必填", -1)
			}
			if lo.Contains(materialIDList, v.MaterialID) {
				return "", timeountRemind, errorx.New("素材id不能重复", -1)
			}
			if v.PlayType == 0 {
				v.PlayType = 1
			}
			if v.PlayType != 1 && v.PlayType != 2 && v.PlayType != 3 {
				return "", timeountRemind, errorx.New("播放模式不正确", -1)
			}
			if v.PlayType == 2 {
				if v.RegularInterval < 1 || v.RegularInterval > 5 {
					return "", timeountRemind, errorx.New("定时播放模式的定时间隔错误", -1)
				}
			} else {
				materials[k].RegularInterval = 0
			}
			materialIDList = append(materialIDList, v.MaterialID)
		}

		materialList, materialCount, _ := dao.Material{}.List(ctx, db, merchantID, strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		if materialCount > 0 {
			for _, v := range materialList {
				MaterialMap[v.ID] = *v
			}
		}
		for k, v := range materials {
			if _, ok := MaterialMap[v.MaterialID]; !ok {
				return "", timeountRemind, errorx.New(fmt.Sprintf("第%d个素材文件不存在", k+1), -1)
			}
			if MaterialMap[v.MaterialID].Type == 1 { // 图片
				if MaterialMap[v.MaterialID].Position != position {
					return "", timeountRemind, errorx.New("当前素材图片不是后屏素材图片", -1)
				}
				totalDuration += v.Duration
			} else if MaterialMap[v.MaterialID].Type == 2 { // 视频
				if MaterialMap[v.MaterialID].Duration > 240 || MaterialMap[v.MaterialID].Duration < 4 {
					return "", timeountRemind, errorx.New("视频素材时长不能超过240秒或小于4秒", -1)
				}
				materials[k].Duration = MaterialMap[v.MaterialID].Duration // 视频时长以数据库为准
				totalDuration += MaterialMap[v.MaterialID].Duration
			}
		}
	} else if position == 3 { // 后屏媒体屏
		if publishType == 1 { // 刊例
			if len(materials) > 5 {
				return "", timeountRemind, errorx.New("投放计划为媒体屏刊例时,最多5张素材", -1)
			}
		}
		var materialIDList = []string{}
		for k, v := range materials {
			if v.MaterialID == "" {
				return "", timeountRemind, errorx.New("素材id不能为空", -1)
			}
			if v.Duration == 0 {
				return "", timeountRemind, errorx.New("素材投放时长必填", -1)
			}
			if lo.Contains(materialIDList, v.MaterialID) {
				return "", timeountRemind, errorx.New("素材id不能重复", -1)
			}
			if v.PlayType == 0 {
				v.PlayType = 1
			}
			if v.PlayType != 1 && v.PlayType != 2 && v.PlayType != 3 {
				return "", timeountRemind, errorx.New("播放模式不正确", -1)
			}
			if v.PlayType == 2 {
				if v.RegularInterval < 1 || v.RegularInterval > 5 {
					return "", timeountRemind, errorx.New("定时播放模式的定时间隔错误", -1)
				}
			} else {
				materials[k].RegularInterval = 0
			}
			materialIDList = append(materialIDList, v.MaterialID)
		}

		materialList, materialCount, _ := dao.Material{}.List(ctx, db, merchantID, strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		if materialCount > 0 {
			for _, v := range materialList {
				MaterialMap[v.ID] = *v
			}
		}
		for k, v := range materials {
			if _, ok := MaterialMap[v.MaterialID]; !ok {
				return "", timeountRemind, errorx.New(fmt.Sprintf("第%d个素材文件不存在", k+1), -1)
			}
			if MaterialMap[v.MaterialID].Type == 1 { // 图片
				// if MaterialMap[v.MaterialID].Position != position {
				// 	return timeountRemind, errorx.New("当前素材图片不是后屏素材图片", -1)
				// }
				totalDuration += v.Duration
			} else if MaterialMap[v.MaterialID].Type == 2 { // 视频
				if MaterialMap[v.MaterialID].Duration > 240 || MaterialMap[v.MaterialID].Duration < 4 {
					return "", timeountRemind, errorx.New("视频素材时长不能超过240秒或小于4秒", -1)
				}
				materials[k].Duration = MaterialMap[v.MaterialID].Duration // 视频时长以数据库为准
				totalDuration += MaterialMap[v.MaterialID].Duration
			}
		}
	}

	// 设备判断
	DeviceList := strings.Split(devices, ",")
	if devices == "" || len(DeviceList) == 0 {
		return "", timeountRemind, errorx.New("投放设备不能为空", -1)
	}
	MerchantDeviceList, _ := dao.MerchantPublishDeviceGroup{}.MerchantAuthorizedDevice(ctx, db, merchantID)
	for _, v := range DeviceList {
		if !lo.Contains(MerchantDeviceList, v) {
			return "", timeountRemind, errorx.New(fmt.Sprintf("设备【%s】没有投放权限", v), -1)
		}
	}
	// 设备时间
	usedDeviceTime := PublishSchedule{}.DeviceUsedTime(ctx, devices, ID, publishType, startTime, endTime)
	for _, item := range usedDeviceTime {
		if totalDuration+item.TotalDuration > 60*10 { // 总时长超过10分钟
			timeountRemind = 1
		}
	}
	DeviceAdminClient, clientErr := client.GetDeviceAdminClient()
	if clientErr != nil {
		logx.Error(context.Background(), "connect device admin error", logx.Any("err", clientErr.Error()))
		err = errorx.New("系统连接错误")
		return
	}

	var locationIDs, districtIDs []string
	for _, robotName := range DeviceList {
		deviceReq := devicec.DeviceDetailReq{
			RobotId: robotName,
		}
		deviceDetail, err1 := DeviceAdminClient.DeviceDetail(context.Background(), &deviceReq)
		if err1 != nil {
			err = errorx.New("获取设备信息错误")
			return
		}
		if deviceDetail.Code == 0 {
			if deviceDetail.Device.LocationId != "" {
				if !lo.Contains(locationIDs, deviceDetail.Device.LocationId) {
					locationIDs = append(locationIDs, deviceDetail.Device.LocationId)
				}
			}
			if deviceDetail.Device.DistrictId != "" {
				if !lo.Contains(districtIDs, deviceDetail.Device.DistrictId) {
					districtIDs = append(districtIDs, deviceDetail.Device.DistrictId)
				}
			}
		}
	}
	locationID := strings.Join(locationIDs, ",")
	districtID := strings.Join(districtIDs, ",")
	if publishType != 2 { // 2 精准
		gender = ""
		age = ""
		tag = ""
		publishCount = 0
	}
	if ID == "" { // 新增
		// 新增投放计划信息
		PublishScheduleID, _ := dao.PublishSchedule{}.Create(ctx, db, name, position, startTime, endTime, totalDuration, pkg.HandlerHtml(details), districtID, locationID, publishType, merchantID, merchantName, userID, userName, int64(len(DeviceList)), detailsShow, gender, age, tag, publishCount, supportActivityID)
		// 新增投放素材信息
		for _, v := range materials {
			dao.PublishScheduleMaterial{}.Create(ctx, db, merchantID, PublishScheduleID, v.MaterialID, v.Duration, v.PlayType, v.RegularInterval)
		}

		// 新增投放设备信息
		for _, v := range DeviceList {
			dao.PublishScheduleDevice{}.Create(ctx, db, merchantID, PublishScheduleID, v, startTime, endTime, int(position))
		}
		id = PublishScheduleID
	} else { // 编辑
		// 编辑投放计划信息
		dao.PublishSchedule{}.Update(ctx, db, ID, merchantID, strings.TrimSpace(name), position,
			startTime, endTime, totalDuration, pkg.HandlerHtml(details), districtID,
			locationID, int64(len(DeviceList)), detailsShow, gender, age, tag, 1, supportActivityID) // 1 用户端编辑后回到未提审状态
		// --------- 投放素材信息 -------------
		var OldMaterialList []string
		materialList, materialCount, _ := dao.PublishScheduleMaterial{}.List(ctx, db, ID, "", 0, 0)
		if materialCount > 0 {
			for _, v := range materialList {
				if v.MaterialID != "" {
					OldMaterialList = append(OldMaterialList, v.MaterialID)
				}
			}
		}
		var MaterialIDList []string
		for _, v := range materials {
			MaterialIDList = append(MaterialIDList, v.MaterialID)
		}
		// 新增 删除
		// deleteMaterial  是 materials没有的 即 需删除
		// addMaterial  是 OldMaterialList没有的 即 新增的
		deleteMaterial, addMaterial := lo.Difference(OldMaterialList, MaterialIDList)
		// 编辑
		saveMaterial := lo.Intersect(OldMaterialList, MaterialIDList)

		// 新增
		for _, v := range materials {
			if lo.Contains(addMaterial, v.MaterialID) {
				dao.PublishScheduleMaterial{}.Create(ctx, db, merchantID, ID, v.MaterialID, v.Duration, v.PlayType, v.RegularInterval)
			}
		}
		// 编辑
		for _, v := range materials {
			if lo.Contains(saveMaterial, v.MaterialID) {
				dao.PublishScheduleMaterial{}.UpdateDuration(ctx, db, ID, v.MaterialID, v.Duration, v.PlayType, v.RegularInterval)
			}
		}
		// 删除
		if len(deleteMaterial) > 0 {
			dao.PublishScheduleMaterial{}.Delete(ctx, db, ID, strings.Join(deleteMaterial, ","))
		}

		// ---------- 投放设备信息 【true 不包含待删除】-----------
		PublishScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", ID, "", int(position), 0, 0, true, 0, 0)
		var OldDeviceList []string
		for _, v := range PublishScheduleDeviceList {
			OldDeviceList = append(OldDeviceList, v.RobotName)
		}
		// 新增 删除
		// deleteDevice  是 DeviceList没有的 即 需删除
		// addDevice  是 OldDeviceList没有的 即 新增的
		deleteDevice, addDevice := lo.Difference(OldDeviceList, DeviceList)
		// 编辑
		saveDevice := lo.Intersect(OldDeviceList, DeviceList)

		// 新增
		for _, robotName := range addDevice {
			dao.PublishScheduleDevice{}.Create(ctx, db, merchantID, ID, robotName, startTime, endTime, int(position))
		}

		// 编辑
		db.Model(&models.PublishScheduleDevice{}).
			Where("schedule_id = ?", ID).
			Where("terminal_type = ?", position).
			Where("robot_name in (?)", saveDevice).
			UpdateColumns(map[string]interface{}{
				"start_time":     startTime,
				"end_time":       endTime,
				"updated_at":     time.Now().Unix(),
				"deleted_at":     0,
				"publish_type":   2, // 编辑
				"publish_time":   0, // 投放时间
				"publish_status": 0, // 未投放
			})

		// 删除
		if len(deleteDevice) > 0 {
			dao.PublishScheduleDevice{}.SetToDelete(ctx, db, ID, strings.Join(deleteDevice, ","), int(position))
		}
		id = ID
	}
	if totalDuration > 60*10 { // 10分钟 超时提醒
		timeountRemind = 1
	}
	return
}

func (PublishSchedule) SaveNoAuth(ctx context.Context, db *gorm.DB, merchantID, merchantName, userID, userName string,
	ID string, position int8, name string, startTime, endTime int64, details string, materials Materials,
	gender, age, tag, devices string, publishType, detailsShow int8, publishCount int64, supportActivityID string) (id string, timeountRemind int, err error) {
	var totalDuration int64
	if position == 0 {
		return "", timeountRemind, errorx.New("投放位置不能为空", -1)
	}
	if position != 1 && position != 2 && position != 3 {
		return "", timeountRemind, errorx.New("投放位置错误", -1)
	}
	if strings.TrimSpace(name) == "" {
		return "", timeountRemind, errorx.New("计划名称不能为空", -1)
	}
	regRes, _ := regexp.MatchString("^[0-9a-zA-Z\u4e00-\u9fa5]{1,12}$", strings.TrimSpace(name))
	if !regRes {
		return "", timeountRemind, errorx.New("计划名称仅限中文英文及数字且最长12个字符", -1)
	}

	// 时间判断
	if startTime == 0 {
		return "", timeountRemind, errorx.New("投放周期开始时间不能为空", -1)
	}
	if endTime == 0 {
		return "", timeountRemind, errorx.New("投放周期结束时间不能为空", -1)
	}
	if endTime <= startTime {
		return "", timeountRemind, errorx.New("投放周期结束时间不能小于开始时间", -1)
	}
	if endTime-startTime > 366*86400 {
		return "", timeountRemind, errorx.New("投放周期最大选择366天", -1)
	}
	if ID == "" { // 新增
		// 1 刊例单素材 2精准 3刊例多素材
		if publishType != 1 && publishType != 2 && publishType != 3 { // 仅新增不允许编辑
			return "", timeountRemind, errorx.New("投放类型错误", -1)
		}
	} else { // 编辑
		scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, merchantID, ID)
		if scheduleInfo == nil || scheduleCount == 0 {
			return "", timeountRemind, errorx.New("投放计划不存在", -1)
		}
		if scheduleInfo.Status != TOAUDIT {
			return "", timeountRemind, errorx.New("投放计划的当前状态不允许编辑", -1)
		}
		publishType = scheduleInfo.PublishType
	}
	if detailsShow != 0 && detailsShow != 1 {
		return "", timeountRemind, errorx.New("素材详情页开关异常", -1)
	}
	// 素材判断
	if len(materials) == 0 {
		return "", timeountRemind, errorx.New("投放素材不能为空", -1)
	}
	if position == 1 { // 前屏售卖屏
		if publishType == 2 { // 精准
			gender, err = CheckGender(gender)
			if err != nil {
				return
			}
			age, err = CheckAge(age)
			if err != nil {
				return
			}
			tag, err = CheckTag(tag)
			if err != nil {
				return
			}
			if publishCount <= 0 || publishCount%100 != 0 {
				return "", timeountRemind, errorx.New("投放次数必须是100的倍数", -1)
			}
			if len(materials) > 1 {
				return "", timeountRemind, errorx.New("投放计划为售卖屏精准时,最多1张素材", -1)
			}
		} else if publishType == 1 { // 刊例单素材
			if len(materials) > 1 {
				return "", timeountRemind, errorx.New("投放计划为售卖屏单素材刊例时,最多1张素材", -1)
			}
		} else if publishType == 3 { // 刊例多素材
			if len(materials) > 5 {
				return "", timeountRemind, errorx.New("投放计划为售卖屏多素材刊例时,最多5张素材", -1)
			}
			detailsShow = 0
			details = ""
		}
		var materialIDList = []string{}
		for k, v := range materials {
			if v.MaterialID == "" {
				return "", timeountRemind, errorx.New("素材id不能为空", -1)
			}
			if v.Duration == 0 {
				return "", timeountRemind, errorx.New("素材投放时长必填", -1)
			}
			if lo.Contains(materialIDList, v.MaterialID) {
				return "", timeountRemind, errorx.New("素材id不能重复", -1)
			}
			if v.PlayType == 0 {
				v.PlayType = 1
			}
			if v.PlayType != 1 && v.PlayType != 2 && v.PlayType != 3 {
				return "", timeountRemind, errorx.New("播放模式不正确", -1)
			}
			if v.PlayType == 2 {
				if v.RegularInterval < 1 || v.RegularInterval > 5 {
					return "", timeountRemind, errorx.New("定时播放模式的定时间隔错误", -1)
				}
			} else {
				materials[k].RegularInterval = 0
			}
			materialIDList = append(materialIDList, v.MaterialID)
		}
		materialList, materialCount, _ := dao.Material{}.List(ctx, db, merchantID, strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		if materialCount > 0 {
			for _, v := range materialList {
				MaterialMap[v.ID] = *v
			}
		}
		for k, v := range materials {
			if _, ok := MaterialMap[v.MaterialID]; !ok {
				return "", timeountRemind, errorx.New(fmt.Sprintf("第%d个素材文件不存在", k+1), -1)
			}
			if MaterialMap[v.MaterialID].Type == 1 { // 图片
				if MaterialMap[v.MaterialID].Position != position {
					return "", timeountRemind, errorx.New("当前素材图片不是售卖屏素材图片", -1)
				}
				totalDuration += v.Duration
			} else {
				return "", timeountRemind, errorx.New("售卖屏投放计划仅限图片素材", -1)
			}
		}
	} else if position == 2 { // 后屏媒体屏
		if publishType == 2 { // 精准
			gender, err = CheckGender(gender)
			if err != nil {
				return
			}
			age, err = CheckAge(age)
			if err != nil {
				return
			}
			tag, err = CheckTag(tag)
			if err != nil {
				return
			}
			if publishCount <= 0 || publishCount%100 != 0 {
				return "", timeountRemind, errorx.New("投放次数必须是100的倍数", -1)
			}
			if len(materials) > 1 {
				return "", timeountRemind, errorx.New("投放计划为媒体屏精准时,最多1张素材", -1)
			}
		} else { // 刊例
			if len(materials) > 5 {
				return "", timeountRemind, errorx.New("投放计划为媒体屏刊例时,最多5张素材", -1)
			}
		}
		var materialIDList = []string{}
		for k, v := range materials {
			if v.MaterialID == "" {
				return "", timeountRemind, errorx.New("素材id不能为空", -1)
			}
			if v.Duration == 0 {
				return "", timeountRemind, errorx.New("素材投放时长必填", -1)
			}
			if lo.Contains(materialIDList, v.MaterialID) {
				return "", timeountRemind, errorx.New("素材id不能重复", -1)
			}
			if v.PlayType == 0 {
				v.PlayType = 1
			}
			if v.PlayType != 1 && v.PlayType != 2 && v.PlayType != 3 {
				return "", timeountRemind, errorx.New("播放模式不正确", -1)
			}
			if v.PlayType == 2 {
				if v.RegularInterval < 1 || v.RegularInterval > 5 {
					return "", timeountRemind, errorx.New("定时播放模式的定时间隔错误", -1)
				}
			} else {
				materials[k].RegularInterval = 0
			}
			materialIDList = append(materialIDList, v.MaterialID)
		}

		materialList, materialCount, _ := dao.Material{}.List(ctx, db, merchantID, strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		if materialCount > 0 {
			for _, v := range materialList {
				MaterialMap[v.ID] = *v
			}
		}
		for k, v := range materials {
			if _, ok := MaterialMap[v.MaterialID]; !ok {
				return "", timeountRemind, errorx.New(fmt.Sprintf("第%d个素材文件不存在", k+1), -1)
			}
			if MaterialMap[v.MaterialID].Type == 1 { // 图片
				if MaterialMap[v.MaterialID].Position != position {
					return "", timeountRemind, errorx.New("当前素材图片不是后屏素材图片", -1)
				}
				totalDuration += v.Duration
			} else if MaterialMap[v.MaterialID].Type == 2 { // 视频
				if MaterialMap[v.MaterialID].Duration > 240 || MaterialMap[v.MaterialID].Duration < 4 {
					return "", timeountRemind, errorx.New("视频素材时长不能超过240秒或小于4秒", -1)
				}
				materials[k].Duration = MaterialMap[v.MaterialID].Duration // 视频时长以数据库为准
				totalDuration += MaterialMap[v.MaterialID].Duration
			}
		}
	} else if position == 3 { // 后屏媒体屏
		if publishType == 1 { // 刊例
			if len(materials) > 5 {
				return "", timeountRemind, errorx.New("投放计划为媒体屏刊例时,最多5张素材", -1)
			}
		}
		var materialIDList = []string{}
		for k, v := range materials {
			if v.MaterialID == "" {
				return "", timeountRemind, errorx.New("素材id不能为空", -1)
			}
			if v.Duration == 0 {
				return "", timeountRemind, errorx.New("素材投放时长必填", -1)
			}
			if lo.Contains(materialIDList, v.MaterialID) {
				return "", timeountRemind, errorx.New("素材id不能重复", -1)
			}
			if v.PlayType == 0 {
				v.PlayType = 1
			}
			if v.PlayType != 1 && v.PlayType != 2 && v.PlayType != 3 {
				return "", timeountRemind, errorx.New("播放模式不正确", -1)
			}
			if v.PlayType == 2 {
				if v.RegularInterval < 1 || v.RegularInterval > 5 {
					return "", timeountRemind, errorx.New("定时播放模式的定时间隔错误", -1)
				}
			} else {
				materials[k].RegularInterval = 0
			}
			materialIDList = append(materialIDList, v.MaterialID)
		}

		materialList, materialCount, _ := dao.Material{}.List(ctx, db, merchantID, strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		if materialCount > 0 {
			for _, v := range materialList {
				MaterialMap[v.ID] = *v
			}
		}
		for k, v := range materials {
			if _, ok := MaterialMap[v.MaterialID]; !ok {
				return "", timeountRemind, errorx.New(fmt.Sprintf("第%d个素材文件不存在", k+1), -1)
			}
			if MaterialMap[v.MaterialID].Type == 1 { // 图片
				// if MaterialMap[v.MaterialID].Position != position {
				// 	return timeountRemind, errorx.New("当前素材图片不是后屏素材图片", -1)
				// }
				totalDuration += v.Duration
			} else if MaterialMap[v.MaterialID].Type == 2 { // 视频
				if MaterialMap[v.MaterialID].Duration > 240 || MaterialMap[v.MaterialID].Duration < 4 {
					return "", timeountRemind, errorx.New("视频素材时长不能超过240秒或小于4秒", -1)
				}
				materials[k].Duration = MaterialMap[v.MaterialID].Duration // 视频时长以数据库为准
				totalDuration += MaterialMap[v.MaterialID].Duration
			}
		}
	}

	// 设备判断
	DeviceList := strings.Split(devices, ",")
	if devices == "" || len(DeviceList) == 0 {
		return "", timeountRemind, errorx.New("投放设备不能为空", -1)
	}
	// MerchantDeviceList, _ := dao.MerchantPublishDeviceGroup{}.MerchantAuthorizedDevice(ctx, db, merchantID)
	// for _, v := range DeviceList {
	// 	if !lo.Contains(MerchantDeviceList, v) {
	// 		return "", timeountRemind, errorx.New(fmt.Sprintf("设备【%s】没有投放权限", v), -1)
	// 	}
	// }
	// 设备时间
	usedDeviceTime := PublishSchedule{}.DeviceUsedTime(ctx, devices, ID, publishType, startTime, endTime)
	for _, item := range usedDeviceTime {
		if totalDuration+item.TotalDuration > 60*10 { // 总时长超过10分钟
			timeountRemind = 1
		}
	}
	DeviceAdminClient, clientErr := client.GetDeviceAdminClient()
	if clientErr != nil {
		logx.Error(context.Background(), "connect device admin error", logx.Any("err", clientErr.Error()))
		err = errorx.New("系统连接错误")
		return
	}

	var locationIDs, districtIDs []string
	for _, robotName := range DeviceList {
		deviceReq := devicec.DeviceDetailReq{
			RobotId: robotName,
		}
		deviceDetail, err1 := DeviceAdminClient.DeviceDetail(context.Background(), &deviceReq)
		if err1 != nil {
			err = errorx.New("获取设备信息错误")
			return
		}
		if deviceDetail.Code == 0 {
			if deviceDetail.Device.LocationId != "" {
				if !lo.Contains(locationIDs, deviceDetail.Device.LocationId) {
					locationIDs = append(locationIDs, deviceDetail.Device.LocationId)
				}
			}
			if deviceDetail.Device.DistrictId != "" {
				if !lo.Contains(districtIDs, deviceDetail.Device.DistrictId) {
					districtIDs = append(districtIDs, deviceDetail.Device.DistrictId)
				}
			}
		}
	}
	locationID := strings.Join(locationIDs, ",")
	districtID := strings.Join(districtIDs, ",")
	if publishType != 2 { // 2 精准
		gender = ""
		age = ""
		tag = ""
		publishCount = 0
	}
	if ID == "" { // 新增
		// 新增投放计划信息
		PublishScheduleID, _ := dao.PublishSchedule{}.Create(ctx, db, name, position, startTime, endTime, totalDuration, pkg.HandlerHtml(details), districtID, locationID, publishType, merchantID, merchantName, userID, userName, int64(len(DeviceList)), detailsShow, gender, age, tag, publishCount, supportActivityID)
		// 新增投放素材信息
		for _, v := range materials {
			dao.PublishScheduleMaterial{}.Create(ctx, db, merchantID, PublishScheduleID, v.MaterialID, v.Duration, v.PlayType, v.RegularInterval)
		}

		// 新增投放设备信息
		for _, v := range DeviceList {
			dao.PublishScheduleDevice{}.Create(ctx, db, merchantID, PublishScheduleID, v, startTime, endTime, int(position))
		}
		id = PublishScheduleID
	} else { // 编辑
		// 编辑投放计划信息
		dao.PublishSchedule{}.Update(ctx, db, ID, merchantID, strings.TrimSpace(name), position,
			startTime, endTime, totalDuration, pkg.HandlerHtml(details), districtID,
			locationID, int64(len(DeviceList)), detailsShow, gender, age, tag, 1, supportActivityID) // 1 用户端编辑后回到未提审状态
		// --------- 投放素材信息 -------------
		var OldMaterialList []string
		materialList, materialCount, _ := dao.PublishScheduleMaterial{}.List(ctx, db, ID, "", 0, 0)
		if materialCount > 0 {
			for _, v := range materialList {
				if v.MaterialID != "" {
					OldMaterialList = append(OldMaterialList, v.MaterialID)
				}
			}
		}
		var MaterialIDList []string
		for _, v := range materials {
			MaterialIDList = append(MaterialIDList, v.MaterialID)
		}
		// 新增 删除
		// deleteMaterial  是 materials没有的 即 需删除
		// addMaterial  是 OldMaterialList没有的 即 新增的
		deleteMaterial, addMaterial := lo.Difference(OldMaterialList, MaterialIDList)
		// 编辑
		saveMaterial := lo.Intersect(OldMaterialList, MaterialIDList)

		// 新增
		for _, v := range materials {
			if lo.Contains(addMaterial, v.MaterialID) {
				dao.PublishScheduleMaterial{}.Create(ctx, db, merchantID, ID, v.MaterialID, v.Duration, v.PlayType, v.RegularInterval)
			}
		}
		// 编辑
		for _, v := range materials {
			if lo.Contains(saveMaterial, v.MaterialID) {
				dao.PublishScheduleMaterial{}.UpdateDuration(ctx, db, ID, v.MaterialID, v.Duration, v.PlayType, v.RegularInterval)
			}
		}
		// 删除
		if len(deleteMaterial) > 0 {
			dao.PublishScheduleMaterial{}.Delete(ctx, db, ID, strings.Join(deleteMaterial, ","))
		}

		// ---------- 投放设备信息 【true 不包含待删除】-----------
		PublishScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", ID, "", int(position), 0, 0, true, 0, 0)
		var OldDeviceList []string
		for _, v := range PublishScheduleDeviceList {
			OldDeviceList = append(OldDeviceList, v.RobotName)
		}
		// 新增 删除
		// deleteDevice  是 DeviceList没有的 即 需删除
		// addDevice  是 OldDeviceList没有的 即 新增的
		deleteDevice, addDevice := lo.Difference(OldDeviceList, DeviceList)
		// 编辑
		saveDevice := lo.Intersect(OldDeviceList, DeviceList)

		// 新增
		for _, robotName := range addDevice {
			dao.PublishScheduleDevice{}.Create(ctx, db, merchantID, ID, robotName, startTime, endTime, int(position))
		}

		// 编辑
		db.Model(&models.PublishScheduleDevice{}).
			Where("schedule_id = ?", ID).
			Where("terminal_type = ?", position).
			Where("robot_name in (?)", saveDevice).
			UpdateColumns(map[string]interface{}{
				"start_time":     startTime,
				"end_time":       endTime,
				"updated_at":     time.Now().Unix(),
				"deleted_at":     0,
				"publish_type":   2, // 编辑
				"publish_time":   0, // 投放时间
				"publish_status": 0, // 未投放
			})

		// 删除
		if len(deleteDevice) > 0 {
			dao.PublishScheduleDevice{}.SetToDelete(ctx, db, ID, strings.Join(deleteDevice, ","), int(position))
		}
		id = ID
	}
	if totalDuration > 60*10 { // 10分钟 超时提醒
		timeountRemind = 1
	}
	return
}

type AdvertisementOrderInfo struct {
	//Id              string  `json:"id"`               // 主键,订单号
	//CreatedAt       int64   `json:"created_at"`       // 创建时间
	// CustomName  string `json:"custom_name"`  // 姓名
	// PhoneNumber string `json:"phone_number"` // 电话
	DeviceIds string `json:"device_ids"` // 设备ID数组
	StartAt   int64  `json:"start_at"`   // 起始时间
	EndAt     int64  `json:"end_at"`     // 终止时间
	//Status          int32   `json:"status"`           // 支付状态
	Remark          string `json:"remark"`           // 备注
	Openid          string `json:"openid"`           // 用户openid
	AdvertisementId string `json:"advertisement_id"` // advertisement_id
	// PayAmount       float32 `json:"pay_amount"`       // 支付金额计算优惠
	// PaidAmount      float32 `json:"paid_amount"`      // 已支付金额
	PaidId string `json:"paid_id"` // 支付订单号
	// DiscountAmount  float32 `json:"discount_amount"`  // 下单时优惠一共减免数
	ScheduleId string `json:"schedule_id"` // 投放计划ID
	PayStatus  int32  `gorm:"-" json:"pay_status"`
}

type PublishScheduleListStruct struct {
	models.PublishSchedule
	Orientation int      `gorm:"-" json:"orientation"`
	DeviceCount int      `gorm:"-" json:"device_count"`
	Devices     []string `gorm:"-" json:"devices"`
	Materials   []struct {
		MaterialID  string `gorm:"-" json:"material_id"`
		FilePath    string `gorm:"-" json:"file_path"`
		Type        int8   `gorm:"-" json:"type"`
		Duration    int64  `gorm:"-" json:"duration"`
		RepeatTimes int64  `gorm:"-" json:"repeat_times"`
	} `gorm:"-" json:"materials"`
}

// 投放计划列表
// Devices 设备列表 逗号分隔多个RobotName
func (PublishSchedule) List(ctx context.Context, merchantID, merchantPath, name string,
	position, status int, startTime, endTime int64, locationID, districtID, Devices string, page, limit int, platform int, amountThresh float32,
) (data []PublishScheduleListStruct, total dao.ScheduleTotal, count int64, err error) {
	db := mysql.NewDB()
	if locationID != "" || districtID != "" {
		// 区县商场获取设备
		DeviceAdminClient, clientErr := client.GetDeviceAdminClient()
		if clientErr != nil {
			logx.Error(context.Background(), "connect device admin error", logx.Any("err", clientErr.Error()))
			err = errorx.New("系统连接错误", -1)
			return
		}

		deviceReq := devicec.AllDeviceListReq{
			MerchantId:   merchantID,
			MerchantPath: merchantPath,
			LocationId:   locationID,
			DistrictId:   districtID,
		}
		DeviceList, err1 := DeviceAdminClient.AllDeviceList(context.Background(), &deviceReq)
		if err1 != nil {
			err = errorx.New("获取设备信息错误", -1)
			return
		}
		var RobotNameList []string
		for _, v := range DeviceList.Data {
			if !lo.Contains(RobotNameList, v.RobotId) {
				RobotNameList = append(RobotNameList, v.RobotId)
			}
		}
		// 拼接devices中传入的设备
		if len(RobotNameList) > 0 {
			Devices = Devices + "," + strings.Join(RobotNameList, ",")
			Devices = strings.TrimPrefix(Devices, ",")
		}
	}
	var PublishScheduleList = []PublishScheduleListStruct{}
	var scheduleIDList []string
	if Devices != "" {
		// 先过滤设备，找到设备对应的schedule_id [true 不包含待删除]
		scheduleList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, merchantID, "", Devices, 0, 0, 0, true, 0, 0)
		for _, v := range scheduleList {
			scheduleIDList = append(scheduleIDList, v.ScheduleID)
		}
		if len(scheduleIDList) == 0 { // 设备找不到计划，返回空
			return PublishScheduleList, total, count, nil
		}
	} else {
		if locationID != "" || districtID != "" {
			return PublishScheduleList, total, count, nil
		}
	}
	scheduleList, count, _ := dao.PublishSchedule{}.List(ctx, db, merchantID, strings.Join(scheduleIDList, ","), name, "", int8(position), int8(status), startTime, endTime, page, limit, platform, amountThresh, "", "", "", "", 0, 0)
	// 总数计算
	total, _ = dao.PublishSchedule{}.Total(ctx, db, merchantID, strings.Join(scheduleIDList, ","), name, "", int8(position), startTime, endTime, "", 0, 0)
	if count == 0 {
		return PublishScheduleList, total, count, nil
	}
	for _, v := range scheduleList {
		item := PublishScheduleListStruct{}
		item.PublishSchedule = *v
		PublishScheduleList = append(PublishScheduleList, item)
	}
	var ScheduleIDList = []string{}
	for _, v := range PublishScheduleList {
		ScheduleIDList = append(ScheduleIDList, v.ID)
	}
	// 投放素材
	ScheduleMaterialList, _ := dao.PublishScheduleMaterial{}.MaterialList(ctx, db, strings.Join(ScheduleIDList, ","))
	var ScheduleMaterialMap = make(map[string][]struct {
		MaterialID  string `gorm:"-" json:"material_id"`
		FilePath    string `gorm:"-" json:"file_path"`
		Type        int8   `gorm:"-" json:"type"`
		Duration    int64  `gorm:"-" json:"duration"`
		RepeatTimes int64  `gorm:"-" json:"repeat_times"`
	})
	for _, v := range ScheduleMaterialList {
		ScheduleMaterialMap[v.ScheduleID] = append(ScheduleMaterialMap[v.ScheduleID], struct {
			MaterialID  string `gorm:"-" json:"material_id"`
			FilePath    string `gorm:"-" json:"file_path"`
			Type        int8   `gorm:"-" json:"type"`
			Duration    int64  `gorm:"-" json:"duration"`
			RepeatTimes int64  `gorm:"-" json:"repeat_times"`
		}{
			MaterialID:  v.MaterialID,
			FilePath:    v.FilePath,
			Type:        v.Type,
			Duration:    v.Duration,
			RepeatTimes: v.RepeatTimes,
		})
	}

	// 投放设备 [true 不包含待删除]
	ScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", strings.Join(ScheduleIDList, ","), "", 0, 0, 0, true, 0, 0)
	var ScheduleDeviceMap = make(map[string][]string)
	for _, v := range ScheduleDeviceList {
		if !lo.Contains(ScheduleDeviceMap[v.ScheduleID], v.RobotName) { // 设备去重
			ScheduleDeviceMap[v.ScheduleID] = append(ScheduleDeviceMap[v.ScheduleID], v.RobotName)
		}
	}
	for k, v := range PublishScheduleList {
		if _, ok := ScheduleMaterialMap[v.ID]; ok {
			PublishScheduleList[k].Materials = ScheduleMaterialMap[v.ID]
		}
		if _, ok := ScheduleDeviceMap[v.ID]; ok {
			PublishScheduleList[k].Devices = ScheduleDeviceMap[v.ID]
		}
		PublishScheduleList[k].DeviceCount = len(PublishScheduleList[k].Devices)
		if v.PublishType == 1 || v.PublishType == 3 { // 刊例 【1刊例单素材，3刊例多素材】
			PublishScheduleList[k].Orientation = 0 // "-"
		} else if len(strings.Split(v.Gender, ",")) == len(define.GenderMap) && len(strings.Split(v.Age, ",")) == len(define.AgeMap) && len(strings.Split(v.Tag, ",")) == len(define.TagMap) {
			PublishScheduleList[k].Orientation = 1 // 默认
		} else {
			PublishScheduleList[k].Orientation = 2 // 定向
		}
	}
	return PublishScheduleList, total, count, nil
}

// 提交审核
func (PublishSchedule) Submit(ctx context.Context, db *gorm.DB, merchantID, ID string) (err error) {
	scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, merchantID, ID)
	if scheduleInfo == nil || scheduleCount == 0 {
		return errorx.New("投放计划不存在", -1)
	}
	if scheduleInfo.Status != TOAUDIT {
		return errorx.New("投放计划的当前状态无法提交审核", -1)
	}
	// 编辑投放计划信息
	dao.PublishSchedule{}.ChangeSatatus(ctx, db, merchantID, ID, []int8{}, AUDITING)
	return
}

// 状态改变
func (PublishSchedule) ChangeSatatus(ctx context.Context, db *gorm.DB, merchantID, ID string, status int8) (err error) {
	scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, merchantID, ID)
	if scheduleInfo == nil || scheduleCount == 0 {
		return errorx.New("投放计划不存在", -1)
	}
	if scheduleInfo.Status != TOAUDIT {
		return errorx.New("投放计划的当前状态无法提交审核", -1)
	}
	// 编辑投放计划信息
	dao.PublishSchedule{}.ChangeSatatus(ctx, db, merchantID, ID, []int8{}, status)
	return
}

// 设置订单ID
func (PublishSchedule) SetOrderID(ctx context.Context, db *gorm.DB, ID, orderID string, payAmount, paidAmount, discountAmount float32, customName, phoneNumber, outTradeNo string) (err error) {

	logx.Info(context.Background(), "Advertising PublishSchedule SetOrderID", logx.Any("ID", ID), logx.Any("orderID", orderID))
	scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, "", ID)
	logx.Info(context.Background(), "Advertising PublishSchedule SetOrderID", logx.Any("scheduleInfo", scheduleInfo), logx.Any("scheduleCount", scheduleCount))
	if scheduleInfo == nil || scheduleCount == 0 {
		return errorx.New("投放计划不存在", -1)
	}
	// 编辑投放计划信息
	dao.PublishSchedule{}.SetOrderID(ctx, db, ID, orderID, payAmount, paidAmount, discountAmount, customName, phoneNumber, outTradeNo)
	return
}

// 终止计划
func (PublishSchedule) End(ctx context.Context, db *gorm.DB, merchantID, ID string) (err error) {
	scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, merchantID, ID)
	if scheduleInfo == nil || scheduleCount == 0 {
		return errorx.New("投放计划不存在", -1)
	}

	if !lo.Contains([]int8{AUDITING, PUBLISHING, TOSTART}, scheduleInfo.Status) {
		return errorx.New("投放计划的当前状态无法终止", -1)
	}
	// 编辑投放计划状态
	dao.PublishSchedule{}.ChangeSatatus(ctx, db, merchantID, ID, []int8{}, FINISHED)
	// 投放设备状态修改
	dao.PublishScheduleDevice{}.SetToDelete(ctx, db, ID, "", int(scheduleInfo.Position))

	// 投放设备 false 包含待删除
	PublishScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", ID, "", int(scheduleInfo.Position), 0, 0, false, 0, 0)
	var deviceList = []struct {
		RobotName    string `json:"robot_name"`
		TerminalType int    `json:"terminal_type"`
	}{}
	for _, v := range PublishScheduleDeviceList {
		deviceList = append(deviceList, struct {
			RobotName    string `json:"robot_name"`
			TerminalType int    `json:"terminal_type"`
		}{v.RobotName, v.TerminalType})
	}
	var devices string
	for _, v := range deviceList {
		devices += "," + v.RobotName + "_" + strconv.Itoa(v.TerminalType)
	}
	devices = strings.TrimPrefix(devices, ",")
	// 提醒计划终止 设备后台
	deviceAdminClient, clientErr := client.GetDeviceAdminClient()
	if clientErr != nil {
		logx.Error(ctx, "connnect error", logx.Any("error", "device_admin client connect error"))
		return
	}

	var params devicec.NoticePublishScheduleChangeReq
	params.Devices = devices
	deviceAdminClient.NoticePublishScheduleChange(ctx, &params)
	return
}

type PublishScheduleDetail struct {
	models.PublishSchedule
	Devices   []string `gorm:"-" json:"devices"`
	Materials []struct {
		MaterialID      string `json:"material_id"`
		FilePath        string `json:"file_path"`
		Name            string `json:"name"`
		Type            int8   `json:"type"`
		Duration        int64  `json:"duration"`
		RepeatTimes     int64  `json:"repeat_times"`
		PlayType        int32  `json:"play_type"`
		RegularInterval int32  `json:"regular_interval"`
	} `gorm:"-" json:"materials"`
}

// Detail 投放计划详情
func (PublishSchedule) Detail(ctx context.Context, merchantID string, ID string) (data PublishScheduleDetail, err error) {
	db := mysql.NewDB()
	// 投放计划详情
	scheduleDetail, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, merchantID, ID)
	if scheduleDetail == nil || scheduleCount == 0 {
		return data, nil
	}
	data.PublishSchedule = *scheduleDetail

	// 投放素材
	ScheduleMaterialList, _ := dao.PublishScheduleMaterial{}.MaterialList(ctx, db, ID)
	for _, v := range ScheduleMaterialList {
		item := struct {
			MaterialID      string `json:"material_id"`
			FilePath        string `json:"file_path"`
			Name            string `json:"name"`
			Type            int8   `json:"type"`
			Duration        int64  `json:"duration"`
			RepeatTimes     int64  `json:"repeat_times"`
			PlayType        int32  `json:"play_type"`
			RegularInterval int32  `json:"regular_interval"`
		}{
			MaterialID:      v.MaterialID,
			FilePath:        v.FilePath,
			Name:            v.Name,
			Type:            v.Type,
			Duration:        v.Duration,
			RepeatTimes:     v.RepeatTimes,
			PlayType:        int32(v.PlayType),
			RegularInterval: int32(v.RegularInterval),
		}
		data.Materials = append(data.Materials, item)
	}
	// 投放设备 [true 不包含待删除]
	var DeviceList = []string{}
	PublishScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", ID, "", int(scheduleDetail.Position), 0, 0, true, 0, 0)
	for _, v := range PublishScheduleDeviceList {
		if !lo.Contains(DeviceList, v.RobotName) {
			DeviceList = append(DeviceList, v.RobotName)
		}
	}
	data.Devices = DeviceList
	return data, nil
}

type ScheduleSimpleDetail struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	Details string `json:"details"`
}

func (PublishSchedule) SimpleDetail(ctx context.Context, ID string) (data ScheduleSimpleDetail, err error) {
	db := mysql.NewDB()
	scheduleDetail, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, "", ID)
	if scheduleDetail == nil || scheduleCount == 0 {
		return data, err
	}
	data.ID = scheduleDetail.ID
	data.Name = scheduleDetail.Name
	data.Details = scheduleDetail.Details
	return
}

type RobotTotalDuration struct {
	RobotName     string `json:"robot_name"`
	TotalDuration int64  `json:"total_duration"`
}

// DeviceUsedTime 获取设备已使用时间
func (PublishSchedule) DeviceUsedTime(ctx context.Context, RobotNames, ScheduleID string, publishType int8, startTime, endTime int64) (UsedTimeCount []RobotTotalDuration) {
	db := mysql.NewDB()
	ScheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, "", ScheduleID)
	if scheduleCount == 0 {
		return
	}
	// 计划对应的素材总计时长
	db.Table(models.PublishScheduleDevice{}.TableName()+" as device").
		Joins(" inner join "+models.PublishSchedule{}.TableName()+" as schedule on schedule.id = device.schedule_id").
		Where("schedule.id != ? and schedule.publish_type = ?", ScheduleID, publishType). // 对应计划类型
		// 时间包含
		// [_____start|Time_____][____________][_____end|Time______]
		Where(
			"(device.start_time <= ? and device.end_time >= ?) "+
				"or (device.start_time >= ? and device.end_time <= ?) "+
				"or (device.start_time <= ? and device.end_time >= ?) ",
			startTime, startTime, startTime, endTime, endTime, endTime,
		).
		Where("device.robot_name in (?) and device.terminal_type = ?", strings.Split(RobotNames, ","), ScheduleInfo.Position). // 设备名称 终端类型
		Where("device.deleted_at = 0 and schedule.deleted_at = 0").
		Where("schedule.status != 5 and schedule.status != 6"). // !(审核失败 || 已结束)
		Group("device.robot_name").
		Select("device.robot_name, sum(schedule.duration) as total_duration"). // 总时长
		Find(&UsedTimeCount)

	return
}

type ScheduleCalendarList struct {
	ID        string `json:"id" gorm:"column:id"`
	Name      string `json:"name" gorm:"column:name"`
	Status    int8   `json:"status" gorm:"column:status"`
	StartTime int64  `json:"start_time" gorm:"column:start_time"`
	EndTime   int64  `json:"end_time" gorm:"column:end_time"`
}

// ScheduleCalendar 投放计划日历
func (PublishSchedule) ScheduleCalendar(ctx context.Context, db *gorm.DB, merchantID string, startTime, endTime int64) (data []ScheduleCalendarList, count int64, err error) {
	if startTime == 0 && endTime == 0 {
		startTime = carbon.Now().StartOfMonth().ToStdTime().Unix()
		endTime = carbon.Now().EndOfMonth().ToStdTime().Unix()
	}
	var ScheduleList = []ScheduleCalendarList{}
	calendarList, calendarCount, _ := dao.PublishSchedule{}.ScheduleCalendar(ctx, db, merchantID, startTime, endTime)
	for _, v := range calendarList {
		ScheduleList = append(ScheduleList, ScheduleCalendarList{
			ID:        v.ID,
			Name:      v.Name,
			Status:    v.Status,
			StartTime: v.StartTime,
			EndTime:   v.EndTime,
		})
	}
	return ScheduleList, calendarCount, nil
}

type PublishScheduleListStructOrderInfo struct {
	models.PublishSchedule
	AdvertisementOrderInfo
	Orientation int      `gorm:"-" json:"orientation"`
	DeviceCount int      `gorm:"-" json:"device_count"`
	Devices     []string `gorm:"-" json:"devices"`
	Materials   []struct {
		MaterialID             string `json:"material_id"`
		FilePath               string `json:"file_path"`
		Name                   string `json:"name"`
		Type                   int8   `json:"type"`
		Duration               int64  `json:"duration"`
		RepeatTimes            int64  `json:"repeat_times"`
		PlayType               int32  `json:"play_type"`
		RegularInterval        int32  `json:"regular_interval"`
		DeviceHasMaterialCount int    `gorm:"-" json:"device_hasmaterial_count"`
		DeviceHasMaterial      string `gorm:"-" json:"device_hasmaterial"`
	} `gorm:"-" json:"materials"`
}

type PublishScheduleDetailOrderInfo struct {
	models.PublishSchedule
	AdvertisementOrderInfo
	Orientation int      `gorm:"-" json:"orientation"`
	DeviceCount int      `gorm:"-" json:"device_count"`
	Devices     []string `gorm:"-" json:"devices"`
	Materials   []struct {
		MaterialID             string `json:"material_id"`
		FilePath               string `json:"file_path"`
		Name                   string `json:"name"`
		Type                   int8   `json:"type"`
		Duration               int64  `json:"duration"`
		RepeatTimes            int64  `json:"repeat_times"`
		PlayType               int32  `json:"play_type"`
		RegularInterval        int32  `json:"regular_interval"`
		DeviceHasMaterialCount int    `gorm:"-" json:"device_hasmaterial_count"`
		DeviceHasMaterial      string `gorm:"-" json:"device_hasmaterial"`
	} `gorm:"-" json:"materials"`
}

// 投放计划列表
// Devices 设备列表 逗号分隔多个RobotName
func (PublishSchedule) ListOrderInfo(ctx context.Context, merchantID, merchantPath, name string,
	position, status int, startTime, endTime int64, locationID, districtID, Devices string, page, limit int, platform int, amountThresh float32,
	payStatus int, customName, phoneNumber, outTradeNo, supportActivityID string, createdStartTime, createdEndTime int64,
) (data []PublishScheduleListStructOrderInfo, total dao.ScheduleTotal, count int64, err error) {
	db := mysql.NewDB()
	if locationID != "" || districtID != "" {
		// 区县商场获取设备
		DeviceAdminClient, clientErr := client.GetDeviceAdminClient()
		if clientErr != nil {
			logx.Error(context.Background(), "connect device admin error", logx.Any("err", clientErr.Error()))
			err = errorx.New("系统连接错误", -1)
			return
		}

		deviceReq := devicec.AllDeviceListReq{
			MerchantId:   merchantID,
			MerchantPath: merchantPath,
			LocationId:   locationID,
			DistrictId:   districtID,
		}
		DeviceList, err1 := DeviceAdminClient.AllDeviceList(context.Background(), &deviceReq)
		if err1 != nil {
			err = errorx.New("获取设备信息错误", -1)
			return
		}
		var RobotNameList []string
		for _, v := range DeviceList.Data {
			if !lo.Contains(RobotNameList, v.RobotId) {
				RobotNameList = append(RobotNameList, v.RobotId)
			}
		}
		// 拼接devices中传入的设备
		if len(RobotNameList) > 0 {
			Devices = Devices + "," + strings.Join(RobotNameList, ",")
			Devices = strings.TrimPrefix(Devices, ",")
		}
	}
	var PublishScheduleList = []PublishScheduleListStructOrderInfo{}
	var scheduleIDList []string
	if Devices != "" {
		// 先过滤设备，找到设备对应的schedule_id [true 不包含待删除]
		scheduleList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, merchantID, "", Devices, 0, 0, 0, true, 0, 0)
		for _, v := range scheduleList {
			scheduleIDList = append(scheduleIDList, v.ScheduleID)
		}
		if len(scheduleIDList) == 0 { // 设备找不到计划，返回空
			return PublishScheduleList, total, count, nil
		}
	} else {
		if locationID != "" || districtID != "" {
			return PublishScheduleList, total, count, nil
		}
	}
	scheduleList, count, _ := dao.PublishSchedule{}.List(ctx, db, merchantID, strings.Join(scheduleIDList, ","), name, "", int8(position), int8(status), startTime, endTime, page, limit, platform, amountThresh, customName, phoneNumber, outTradeNo, supportActivityID, createdStartTime, createdEndTime)
	// 总数计算
	total, _ = dao.PublishSchedule{}.Total(ctx, db, merchantID, strings.Join(scheduleIDList, ","), name, "", int8(position), startTime, endTime, supportActivityID, createdStartTime, createdEndTime)
	if count == 0 {
		return PublishScheduleList, total, count, nil
	}
	for _, v := range scheduleList {
		item := PublishScheduleListStructOrderInfo{}
		item.PublishSchedule = *v
		PublishScheduleList = append(PublishScheduleList, item)
	}
	var ScheduleIDList = []string{}
	for _, v := range PublishScheduleList {
		ScheduleIDList = append(ScheduleIDList, v.ID)
	}
	// 投放素材
	ScheduleMaterialList, _ := dao.PublishScheduleMaterial{}.MaterialList(ctx, db, strings.Join(ScheduleIDList, ","))
	var ScheduleMaterialMap = make(map[string][]struct {
		MaterialID             string `json:"material_id"`
		FilePath               string `json:"file_path"`
		Name                   string `json:"name"`
		Type                   int8   `json:"type"`
		Duration               int64  `json:"duration"`
		RepeatTimes            int64  `json:"repeat_times"`
		PlayType               int32  `json:"play_type"`
		RegularInterval        int32  `json:"regular_interval"`
		DeviceHasMaterialCount int    `gorm:"-" json:"device_hasmaterial_count"`
		DeviceHasMaterial      string `gorm:"-" json:"device_hasmaterial"`
	})
	for _, v := range ScheduleMaterialList {
		ScheduleMaterialMap[v.ScheduleID] = append(ScheduleMaterialMap[v.ScheduleID], struct {
			MaterialID             string `json:"material_id"`
			FilePath               string `json:"file_path"`
			Name                   string `json:"name"`
			Type                   int8   `json:"type"`
			Duration               int64  `json:"duration"`
			RepeatTimes            int64  `json:"repeat_times"`
			PlayType               int32  `json:"play_type"`
			RegularInterval        int32  `json:"regular_interval"`
			DeviceHasMaterialCount int    `gorm:"-" json:"device_hasmaterial_count"`
			DeviceHasMaterial      string `gorm:"-" json:"device_hasmaterial"`
		}{
			MaterialID:  v.MaterialID,
			FilePath:    v.FilePath,
			Type:        v.Type,
			Duration:    v.Duration,
			RepeatTimes: v.RepeatTimes,
		})
	}

	// 投放设备 [true 不包含待删除]
	ScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", strings.Join(ScheduleIDList, ","), "", 0, 0, 0, true, 0, 0)
	var ScheduleDeviceMap = make(map[string][]string)
	for _, v := range ScheduleDeviceList {
		if !lo.Contains(ScheduleDeviceMap[v.ScheduleID], v.RobotName) { // 设备去重
			ScheduleDeviceMap[v.ScheduleID] = append(ScheduleDeviceMap[v.ScheduleID], v.RobotName)
		}
	}

	client, err := client.GetMiniAppClient()
	if err != nil {
		return
	}
	var PublishScheduleListNew = []PublishScheduleListStructOrderInfo{}
	for k, v := range PublishScheduleList {
		if _, ok := ScheduleMaterialMap[v.ID]; ok {
			PublishScheduleList[k].Materials = ScheduleMaterialMap[v.ID]
		}

		for mk, _ := range PublishScheduleList[k].Materials {
			devices, _, _ := dao.PublishScheduleDevicePlaylist{}.List(ctx, db, PublishScheduleList[k].ID, "", 0, 0, 99999)

			for dk, _ := range devices {
				if devices[dk].MaterialID == PublishScheduleList[k].Materials[mk].MaterialID {
					PublishScheduleList[k].Materials[mk].DeviceHasMaterialCount += 1
					PublishScheduleList[k].Materials[mk].DeviceHasMaterial += devices[dk].RobotName
					if dk < len(devices)-1 {
						PublishScheduleList[k].Materials[mk].DeviceHasMaterial += ","
					}
				}
			}
		}

		if _, ok := ScheduleDeviceMap[v.ID]; ok {
			PublishScheduleList[k].Devices = ScheduleDeviceMap[v.ID]
		}
		PublishScheduleList[k].DeviceCount = len(PublishScheduleList[k].Devices)
		if v.PublishType == 1 || v.PublishType == 3 { // 刊例 【1刊例单素材，3刊例多素材】
			PublishScheduleList[k].Orientation = 0 // "-"
		} else if len(strings.Split(v.Gender, ",")) == len(define.GenderMap) && len(strings.Split(v.Age, ",")) == len(define.AgeMap) && len(strings.Split(v.Tag, ",")) == len(define.TagMap) {
			PublishScheduleList[k].Orientation = 1 // 默认
		} else {
			PublishScheduleList[k].Orientation = 2 // 定向
		}

		var params mini_app.GetAdvertisementOrderReq

		if PublishScheduleList[k].OrderID == "" {
			PublishScheduleListNew = append(PublishScheduleListNew, PublishScheduleList[k])

		} else {
			params.Id = PublishScheduleList[k].OrderID
			resp, err1 := client.GetAdvertisementOrder(ctx, &params)
			if err1 != nil {
				err = err1
				return
			}
			// 将resp的内容填写到data里
			if resp != nil && resp.Data != nil {

				PublishScheduleList[k].CustomName = resp.Data.CustomName
				PublishScheduleList[k].PhoneNumber = resp.Data.PhoneNumber
				PublishScheduleList[k].DeviceIds = resp.Data.DeviceIds
				PublishScheduleList[k].StartAt = resp.Data.StartAt
				PublishScheduleList[k].EndAt = resp.Data.EndAt
				PublishScheduleList[k].Remark = resp.Data.Remark
				PublishScheduleList[k].Openid = resp.Data.Openid
				PublishScheduleList[k].AdvertisementId = resp.Data.AdvertisementId
				PublishScheduleList[k].PayAmount = resp.Data.PayAmount
				PublishScheduleList[k].PaidAmount = resp.Data.PaidAmount
				PublishScheduleList[k].PaidId = resp.Data.PaidId
				PublishScheduleList[k].DiscountAmount = resp.Data.DiscountAmount

				PublishScheduleList[k].OrderID = resp.Data.Id
				PublishScheduleList[k].PayStatus = resp.Data.Status

				if payStatus > 0 {
					if PublishScheduleList[k].PayStatus == int32(payStatus) {
						PublishScheduleListNew = append(PublishScheduleListNew, PublishScheduleList[k])
					}
				} else {
					PublishScheduleListNew = append(PublishScheduleListNew, PublishScheduleList[k])
				}
			}
		}
	}
	return PublishScheduleListNew, total, count, nil
}

// Detail 投放计划详情
func (PublishSchedule) DetailOrderInfo(ctx context.Context, merchantID string, ID string) (data PublishScheduleDetailOrderInfo, err error) {
	db := mysql.NewDB()
	// 投放计划详情
	scheduleDetail, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, merchantID, ID)
	if scheduleDetail == nil || scheduleCount == 0 {
		return data, nil
	}
	data.PublishSchedule = *scheduleDetail

	// 投放素材
	ScheduleMaterialList, _ := dao.PublishScheduleMaterial{}.MaterialList(ctx, db, ID)
	for _, v := range ScheduleMaterialList {
		item := struct {
			MaterialID             string `json:"material_id"`
			FilePath               string `json:"file_path"`
			Name                   string `json:"name"`
			Type                   int8   `json:"type"`
			Duration               int64  `json:"duration"`
			RepeatTimes            int64  `json:"repeat_times"`
			PlayType               int32  `json:"play_type"`
			RegularInterval        int32  `json:"regular_interval"`
			DeviceHasMaterialCount int    `gorm:"-" json:"device_hasmaterial_count"`
			DeviceHasMaterial      string `gorm:"-" json:"device_hasmaterial"`
		}{
			MaterialID:      v.MaterialID,
			FilePath:        v.FilePath,
			Name:            v.Name,
			Type:            v.Type,
			Duration:        v.Duration,
			RepeatTimes:     v.RepeatTimes,
			PlayType:        int32(v.PlayType),
			RegularInterval: int32(v.RegularInterval),
		}
		data.Materials = append(data.Materials, item)
	}

	for mk, _ := range data.Materials {
		devices, _, _ := dao.PublishScheduleDevicePlaylist{}.List(ctx, db, data.ID, "", 0, 0, 99999)

		for dk, _ := range devices {
			if devices[dk].MaterialID == data.Materials[mk].MaterialID {
				data.Materials[mk].DeviceHasMaterialCount += 1
				data.Materials[mk].DeviceHasMaterial += devices[dk].RobotName
				if dk < len(devices)-1 {
					data.Materials[mk].DeviceHasMaterial += ","
				}
			}
		}
	}
	// 投放设备 [true 不包含待删除]
	var DeviceList = []string{}
	PublishScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", ID, "", int(scheduleDetail.Position), 0, 0, true, 0, 0)
	for _, v := range PublishScheduleDeviceList {
		if !lo.Contains(DeviceList, v.RobotName) {
			DeviceList = append(DeviceList, v.RobotName)
		}
	}

	client, err := client.GetMiniAppClient()
	if err != nil {
		return
	}
	var params mini_app.GetAdvertisementOrderReq
	params.Id = ID
	resp, err := client.GetAdvertisementOrder(ctx, &params)
	if err != nil {
		return
	}

	data.Devices = DeviceList
	data.DeviceCount = int(data.PublishSchedule.DeviceCount)
	data.OutTradeNo = data.OutTradeNo

	// 将resp的内容填写到data里
	if resp != nil && resp.Data != nil {
		data.CustomName = resp.Data.CustomName
		data.PhoneNumber = resp.Data.PhoneNumber
		data.DeviceIds = resp.Data.DeviceIds
		data.StartAt = resp.Data.StartAt
		data.EndAt = resp.Data.EndAt
		data.Remark = resp.Data.Remark
		data.Openid = resp.Data.Openid
		data.AdvertisementId = resp.Data.AdvertisementId
		data.PayAmount = resp.Data.PayAmount
		data.PaidAmount = resp.Data.PaidAmount
		data.PaidId = resp.Data.PaidId
		data.DiscountAmount = resp.Data.DiscountAmount

		data.OrderID = resp.Data.Id
		data.PayStatus = resp.Data.Status

	}

	return data, nil
}
